package org.springframework.security.web.authentication.rememberme;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;

import java.io.IOException;

/**
 * @author Dillon
 * @date 2024/7/14
 * @slogan 致敬大师 致敬未来的你
 * @desc 记住我处理器处理逻辑
 */
public class RememberMeAuthenticationFilter extends GenericFilterBean implements ApplicationEventPublisherAware {

	/**
	 * 上下文缓存策略
	 */
	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
			.getContextHolderStrategy();

	/**
	 * 事件发布器
	 */
	private ApplicationEventPublisher eventPublisher;

	/**
	 * 自定义成功处理器
	 */
	private AuthenticationSuccessHandler successHandler;

	/**
	 * 认证管理器 providerManager
	 */
	private final AuthenticationManager authenticationManager;

	/**
	 * 记住我实现类
	 */
	private final RememberMeServices rememberMeServices;

	/**
	 * 上下文持久化策略
	 */
	private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();

	/**
	 * 构造函数
	 *
	 * @param authenticationManager 认证管理器
	 * @param rememberMeServices    记住我实现类
	 */
	public RememberMeAuthenticationFilter(AuthenticationManager authenticationManager,
			RememberMeServices rememberMeServices) {
		Assert.notNull(authenticationManager, "authenticationManager cannot be null");
		Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
		this.authenticationManager = authenticationManager;
		this.rememberMeServices = rememberMeServices;
	}

	/**
	 * 后验校验
	 */
	@Override
	public void afterPropertiesSet() {
		Assert.notNull(this.authenticationManager, "authenticationManager must be specified");
		Assert.notNull(this.rememberMeServices, "rememberMeServices must be specified");
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// 临界值判断，如果已认证，直接吐过
		if (this.securityContextHolderStrategy.getContext().getAuthentication() != null) {
			this.logger.debug(LogMessage
					.of(() -> "记住我无须认证，权限认证已认证成功: '" + this.securityContextHolderStrategy.getContext().getAuthentication() + "'"));
			chain.doFilter(request, response);
			return;
		}
		// 执行自动认证
		Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
		// 认证成功处理逻辑
		if (rememberMeAuth != null) {
			try {
				// 再次尝试身份认证，信息可能改变
				rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
				// 初始化上下文缓存 并保存
				SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
				context.setAuthentication(rememberMeAuth);
				this.securityContextHolderStrategy.setContext(context);
				// 成功模板方法调用
				onSuccessfulAuthentication(request, response, rememberMeAuth);
				// 日志记录
				this.logger.debug(LogMessage.of(() -> "SecurityContextHolder populated with remember-me token: '"
						+ this.securityContextHolderStrategy.getContext().getAuthentication() + "'"));
				// 记住我持久化
				this.securityContextRepository.saveContext(context, request, response);
				// 事件发布
				if (this.eventPublisher != null) {
					this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
							this.securityContextHolderStrategy.getContext().getAuthentication(), this.getClass()));
				}
				// 自定义成功处理器回调
				if (this.successHandler != null) {
					this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
					return;
				}
			} catch (AuthenticationException ex) {
				this.logger.debug(LogMessage
								.format("SecurityContextHolder not populated with remember-me token, as AuthenticationManager "
										+ "rejected Authentication returned by RememberMeServices: '%s'; "
										+ "invalidating remember-me token", rememberMeAuth),
						ex);
				// 清空记住我持久化
				this.rememberMeServices.loginFail(request, response);
				// 自定义失败处理器回调
				onUnsuccessfulAuthentication(request, response, ex);
			}
		}
		// 执行调用
		chain.doFilter(request, response);
	}

	/**
	 * 模板方法 记住我认证成功回调方法
	 *
	 * @param request    请求对象
	 * @param response   响应对象
	 * @param authResult 认证包装类
	 */
	protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			Authentication authResult) {
	}

	/**
	 * 模板方法，记住我执行失败回调方法
	 *
	 * @param request  请求对象
	 * @param response 响应对象
	 * @param failed   认证异常
	 */
	protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException failed) {
	}

	/**
	 * 获取记住我处理实现类 默认 TokenBasedRememberMeServices
	 *
	 * @return 记住我实现类
	 */
	public RememberMeServices getRememberMeServices() {
		return this.rememberMeServices;
	}

	/**
	 * 设置之间发布器
	 *
	 * @param eventPublisher 时间发布器
	 */
	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
		this.eventPublisher = eventPublisher;
	}

	/**
	 * 设置记住我认证成功调用处理器
	 *
	 * @param successHandler 成功处理器
	 */
	public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) {
		Assert.notNull(successHandler, "成功处理器不能为空");
		this.successHandler = successHandler;
	}

	/**
	 * 设置上下文持久类
	 *
	 * @param securityContextRepository 上下文持久类
	 */
	public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
		Assert.notNull(securityContextRepository, "持久类不能为空");
		this.securityContextRepository = securityContextRepository;
	}

	/**
	 * 设置缓存实现类
	 *
	 * @param securityContextHolderStrategy 缓存实现类
	 */
	public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
		Assert.notNull(securityContextHolderStrategy, "缓存实现类不能为空");
		this.securityContextHolderStrategy = securityContextHolderStrategy;
	}

}
